#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

#define VIS_DEBUG

typedef unsigned long uint;

// externs
unsigned long get_total_memory(void);
void A20Enable(void);

// consts
#define PAGE_SIZE					(4096)
#define VM_BASE						(0xC0000000L)
#define KERNEL_BASE					(0x110000L)

// error codes
#define IMGID_MAGIC_INCORRECT		(101)
#define IMGID_INVALID_KRNL_SIZE		(102)
#define START_MAGIC_INCORRECT		(103)
#define CANNOT_OPEN_SYSCFG_FILE		(104)
#define UNEXPECTED_END_OF_LINE		(105)
#define EXPECT_SYMBOL_EQUAL			(106)

int EnterDosFlatMode(void);

#define IMG_ID_MAGIC		(0xDEADBEEFL)
#define IMG_START_MAGIC		(0xFEEDBAC4L)
#define KRNL_BASE_ADDR		(0x100000L)

typedef struct image_identifier
{
	unsigned long magic;
	unsigned long image_size;		// size of this structure not included
	unsigned long entry_addr;
	unsigned long sysdata_addr;
}
image_identifier_t;

image_identifier_t imgid = {0};

int test_krnlfile(const char *filename)
{
	FILE *fp;
	int errcode;
	long filesize;
	unsigned long start_magic;
	
	assert(NULL != filename);
	
	fp = fopen(filename, "rb");
	if (NULL == fp)
		return 1;
	
	fseek(fp, 0, SEEK_END);
	filesize = ftell(fp);

	fseek(fp, filesize - sizeof(image_identifier_t), SEEK_SET);
	fread(&imgid, sizeof(image_identifier_t), 1, fp);

	if (IMG_ID_MAGIC != imgid.magic)
	{
		errcode = IMGID_MAGIC_INCORRECT;
		goto errhandler;
	}
	if (imgid.image_size > 100000000)
	{
		errcode = IMGID_INVALID_KRNL_SIZE;
		goto errhandler;
	}
	
	fseek(fp, filesize - sizeof(image_identifier_t) - imgid.image_size - 4, SEEK_SET);
	fread(&start_magic, 4, 1, fp);
	if (IMG_START_MAGIC != start_magic)
	{
		errcode = START_MAGIC_INCORRECT;
		goto errhandler;
	}
	
	fclose(fp);
	return 0;
	
errhandler:
	printf("Invalid viskrnl binary (E%u)\n", errcode);
	fclose(fp);
	return errcode;
}

// the following structure defines the system parameter
// must be passed to the viskrnl
typedef struct sys_config
{
	unsigned long total_memory;
	unsigned long kernel_size;

	// this indicates the size of the page file
	// the unit of this parameter is 4K
	unsigned long swap_size_limit;
}
sys_config_t;

sys_config_t sys_cfg;

void init_sys_config(void)
{
	memset(&sys_cfg, 0, sizeof(sys_config_t));
	sys_cfg.total_memory = get_total_memory();
	sys_cfg.kernel_size = imgid.image_size;
	sys_cfg.swap_size_limit = sys_cfg.total_memory / PAGE_SIZE * 2;

#ifdef VIS_DEBUG
	printf("total memory: %lu\n", sys_cfg.total_memory);
	printf("kernel_size: %lu\n", sys_cfg.kernel_size);
	printf("swap size limit: %lu\n", sys_cfg.swap_size_limit);
#endif
}

static unsigned int to_ulongint(char *args)
{
	unsigned long ret;
	sscanf(args, "%lu", &ret);
	return ret;
}

static char* kill_space(char *str)
{
	for (;*str && (*str == ' ' || *str == '\t'); str++);
	return str;
}

#define SYS_CFG_PARA_NAME_UNKNOWN			(0)
#define SYS_CFG_PARA_NAME_TOTAL_MEMORY		(1)
#define SYS_CFG_PARA_NAME_SWAP_SIZE_LIMIT	(2)

static int check_parameter_name(char *name)
{
	int ret = SYS_CFG_PARA_NAME_UNKNOWN;
	if (!strcmp(name, "TotalSysMemory"))
		ret = SYS_CFG_PARA_NAME_TOTAL_MEMORY;
	else if (!strcmp(name, "PageFileLimit"))
		ret = SYS_CFG_PARA_NAME_SWAP_SIZE_LIMIT;
	// todo: else

	return ret;
}

static int handle_parameter(int para_id, char *val)
{
	switch (para_id)
	{
	case SYS_CFG_PARA_NAME_UNKNOWN:
		return SYS_CFG_PARA_NAME_UNKNOWN;

	case SYS_CFG_PARA_NAME_TOTAL_MEMORY: {
		unsigned long tmp = to_ulongint(val);
		if (tmp && tmp < sys_cfg.total_memory)
			sys_cfg.total_memory = tmp;

#ifdef VIS_DEBUG
		printf("handle_parameter: total memory: %lu\n",
			sys_cfg.total_memory);
#endif
	}	break;

	case SYS_CFG_PARA_NAME_SWAP_SIZE_LIMIT:
		sys_cfg.swap_size_limit = to_ulongint(val);

#ifdef VIS_DEBUG
		printf("handle_parameter: swap_size_limit: %lu\n",
			sys_cfg.swap_size_limit);
#endif
		break;

	// todo: other items
	}
	return 0;
}

static int load_sysconfig(char *filename)
{
	int line = 0;
	char buffer[256];

	FILE *fp = fopen(filename, "r");
	if (NULL == fp)
	{
		printf("Cannot open \"%s\" (E%u)\n", filename,
			CANNOT_OPEN_SYSCFG_FILE);
		return CANNOT_OPEN_SYSCFG_FILE;
	}

	while (fgets(buffer, 256, fp))
	{
		int ret;
		char store;
		int para_id;
		char *pnend;
		char *str = buffer;
		str = kill_space(str);

		line++;

		// check for the parameter name
		for (pnend = str; *pnend && *pnend != ' '
			&& *pnend != '\t' && *pnend != '='; pnend++);
		if (!*pnend)
		{
			printf("Error in parse line %u (E%u)\n", line,
				UNEXPECTED_END_OF_LINE);
			fclose(fp);
			return UNEXPECTED_END_OF_LINE;
		}
		store = *pnend;	// temporary store the value
		*pnend = '\0';
		para_id = check_parameter_name(str);
		*pnend = store;	// restore the original value
		str = pnend;

		// handle '='
		str = kill_space(str);
		if (*str != '=')
		{
			printf("Error in parse line %u (E%u)\n", line,
				EXPECT_SYMBOL_EQUAL);
			fclose(fp);
			return EXPECT_SYMBOL_EQUAL;
		}
		str = kill_space(++str);

		// check the parameter value
		for (pnend = str; *pnend && *pnend != ' '
			&& *pnend != '\t' && *pnend != '\n'
			&& *pnend != '\r'; pnend++);
		*pnend = '\0';

		ret = handle_parameter(para_id, str);
		if (ret)
		{
			fclose(fp);
			return ret;
		}
	}
	fclose(fp);
	return 0;
}

static void load_krnl_to_memory(FILE *fp, uint pAddr, uint Length)
{
	char buffer[512];
	for (; Length > 512L; Length -= 512L)
	{
		fread(buffer, 512, 1, fp);
		CopyMemory(pAddr, (void *)buffer, 512);
		pAddr += 512L;
	}
	if (Length > 0)
	{
		fread(buffer, (size_t)Length, 1, fp);
		CopyMemory(pAddr, (void *)buffer, (int)Length);
	}
}

int load_kernel(const char *filename)
{
	FILE *fp;
	long filesize;

	if (!imgid.image_size)
		return 2;

	fp = fopen(filename, "rb");
	if (NULL == fp)
		return 1;

	fseek(fp, 0, SEEK_END);
	filesize = ftell(fp);

	fseek(fp, filesize - sizeof(image_identifier_t) - imgid.image_size, SEEK_SET);

	// load the kernel file to memory
	load_krnl_to_memory(fp, KERNEL_BASE, imgid.image_size);
	fclose(fp);

	// copy the sys config data
	CopyMemory(imgid.sysdata_addr - VM_BASE, &sys_cfg, sizeof(sys_config_t));
	return 0;
}

static void usage(void)
{
	printf("Usage: viskrnl configfile\n");
}

static void force_switch_to_text_mode(void)
{
	asm	mov ax, 3;
	asm int 0x10;
}

unsigned int enter_protect_mode(uint LinearAddress);

int main(int argc, char *argv[])
{
	if (argc > 2)
	{
		usage();
		return 0;
	}

	force_switch_to_text_mode();

	if (test_krnlfile(argv[0]))
		return 1;
	
	// first init the default sys config
	init_sys_config();

	// then try to load from user defined
	// config file
	if (argc == 2 && load_sysconfig(argv[1]))
		return 2;

	if (EnterDosFlatMode())
		return 3;

	// load kernel and sys data into specific memory
	if (load_kernel(argv[0]))
		return 4;

	enter_protect_mode(imgid.entry_addr);
	return 0;
}